# -*- coding: utf-8 -*-
import os
import subprocess
import time
import logging
import argparse
import shutil
import paramiko
# from appium.options.android import UiAutomator2Options
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import pytesseract
from PIL import Image
import cv2
import numpy as np
import json
import pytz
from datetime import datetime

logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
logger = logging

APP = "XHS"

app_package_map = {
    "XHS": ("com.xingin.xhs", ".index.v2.IndexActivityV2")
}


class XHSAppiumOperate:
    def __init__(self, **kwargs):
        self.post_content = ""
        self.note_link = None
        self.clean_title = None
        self.wait_time = None
        self.screen_width = None
        self.screen_height = None

        self.deviceName = kwargs.get("deviceName")
        # self.deviceName = "FMR0223A31045650"
        self.appium_url = kwargs.get("appium_url")
        self.platformVersion = kwargs.get("platformVersion")
        self.accountId = kwargs.get("accountId", "0000")
        self.systemPort = kwargs.get("systemPort")
        self.adbPort = kwargs.get("adbPort")
        self.analyze_n_posts = kwargs.get("analyze_n_posts")

        self.driver = None
        self.max_retries = 3
        self.failure_count = 0
        self.view_count = 0
        self.like_count = 0
        self.comment_count = 0
        self.follow_count = 0
        self.current_post_title = ""

        self.home_dir = os.path.expanduser("~")
        # 设置上海时区（北京时间）
        shanghai_tz = pytz.timezone('Asia/Shanghai')
        # 获取当前时间并格式化
        self.current_time = datetime.now(shanghai_tz).strftime('%Y%m%d')
        self.local_dir = os.path.join(self.home_dir, "ocr", self.current_time, f"{self.deviceName}#{self.accountId}")
        os.makedirs(self.local_dir, exist_ok=True)  # 确保目录存在

    def run(self):
        while self.max_retries > 0:
            try:
                if self.driver:
                    self.driver.quit()
                self.connection()
                logger.info(f"设备 {self.deviceName} 连接成功")
                time.sleep(3)
                logger.info("开始执行小红书【数据分析截图】任务...")
                self.script()
                break
            except Exception as e:
                logger.error(f"发生异常: {e}")
                self.failure_count += 1
                self.max_retries -= 1
                if self.max_retries == 0:
                    self.normal_end()
                    raise Exception("任务失败")
        logger.info("任务结束")

    @staticmethod
    def execute_adb_command(adb_command):
        """
        执行单条 ADB 命令

        :param adb_command: 完整的 ADB 命令字符串
        :return: 命令执行结果（stdout）
        """
        try:
            logger.info(f"执行 ADB 命令: {adb_command}")
            result = subprocess.run(
                adb_command,
                shell=True,
                check=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            logger.info(f"命令执行成功: {adb_command}")
            return result.stdout.decode('utf-8')
        except subprocess.CalledProcessError as e:
            logger.error(f"命令执行失败: {adb_command}")
            logger.error(f"错误信息: {e.stderr.decode('utf-8')}")

    def initialize_device_settings(self):
        """
        执行设备初始化所需的 ADB 命令
        """
        logger.info("开始初始化设备设置")

        commands = [
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global adb_keep_usb_enabled 1",
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global miui_optimization 0",
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global tcp_keepalive 1",
            f"adb -s {self.deviceName} -P {self.adbPort} shell dumpsys battery unplug",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm enable io.appium.uiautomator2.server",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm enable io.appium.uiautomator2.server.test",
            f"adb -s {self.deviceName} -P {self.adbPort} shell settings put global background_process_limit 5",
            f"adb -s {self.deviceName} -P {self.adbPort} shell am set-standby-bucket io.appium.uiautomator2.server ACTIVE",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm trim-caches 500M",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm clear io.appium.uiautomator2.server",
            f"adb -s {self.deviceName} -P {self.adbPort} shell pm clear io.appium.uiautomator2.server.test"
        ]

        for cmd in commands:
            try:
                self.execute_adb_command(cmd)
            except Exception as e:
                logger.error(f"执行命令失败: {cmd}")
                logger.error(f"错误详情: {e}")

    def connection(self):
        
        # 设置上海时区（北京时间）
        shanghai_tz = pytz.timezone('Asia/Shanghai')
        # 获取当前时间并格式化
        # 计算前两天的日期目录
        two_days_ago = (datetime.now(shanghai_tz) - timedelta(days=2)).strftime('%Y%m%d')
        two_days_ago_dir = os.path.join(self.home_dir, "ocr", two_days_ago)
        # 若前两天的目录存在，则清空
        if os.path.exists(two_days_ago_dir):
            try:
                shutil.rmtree(two_days_ago_dir)
                logger.info(f"已清空前两天的本地目录: {two_days_ago_dir}")
            except Exception as e:
                logger.error(f"清空本地前两天目录失败: {e}")
                
        self.initialize_device_settings()
        desired_caps = {
            "platformName": "Android",
            # "udid": "FMR0223A31045650",
            "udid": self.deviceName,
            "platformVersion": self.platformVersion,
            "deviceName": self.deviceName,
            "automationName": "UiAutomator2",
            "appPackage": app_package_map[APP][0],
            "appActivity": app_package_map[APP][1],
            "noReset": True,
            "fullReset": False,
            "systemPort": int(self.systemPort),
            "adbPort": int(self.adbPort),
            "appWaitForLaunch": True,
            "noSign": True,
            "autoLaunch": True,
            "newCommandTimeout": 500,
            "skipLogcatCapture": True,
            "disableWindowAnimation": True,
            "disableAndroidWatchers": True,
            "adbExecTimeout": 90000,
            "uiautomator2ServerInstallTimeout": 90000,
            "skipDeviceInitialization": True,
        }

        # self.driver = webdriver.Remote(self.appium_url, options=UiAutomator2Options().load_capabilities(desired_caps))
        self.driver = webdriver.Remote(self.appium_url, desired_caps)
        self.screen_width, self.screen_height = self.get_window_size()
        self.wait_time = 2

    def script(self):
        """执行数据分析截图任务"""
        if self.accountId == "0000":
            logger.error("平台没有传递accountId参数")
            raise Exception("平台没有传递accountId参数")
        try:
            edit_draft = self.find_element_with_retry(
                by=AppiumBy.ANDROID_UIAUTOMATOR,
                value='new UiSelector().textContains("继续编辑")',
                timeout=5
            )
            # edit_draft.click()
            logger.error(f"已经找到【编辑图文笔记吗？】弹窗，执行关闭操作")
            cancel_button = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value='(//android.widget.FrameLayout[@resource-id="com.xingin.xhs:id/0_resource_name_obfuscated"])[2]/android.widget.ImageView',
                timeout=5
            )
            cancel_button.click()
        except Exception as e:
            logger.error(f"未找到【继续编辑图文笔记吗？】弹窗: {e}")

        logger.info("开始执行【数据分析截图】任务")
        # Step 1: 打开主页
        user_profile_url, nickname, following_count, fans, likes_collect_count = self._enter_profile_page()

        profile_data = {
            "user_profile_url": user_profile_url,
            "nickname": nickname,
            "following_count": following_count,
            "fans": fans,
            "likes_collect_count": likes_collect_count
        }
        profile_url_file = os.path.join(self.local_dir, "profile_url.json")
        try:
            with open(profile_url_file, 'w', encoding='utf-8') as f:
                json.dump(profile_data, f, ensure_ascii=False, indent=4)
            logger.info(f"个人主页信息已保存到: {profile_url_file}")
        except Exception as e:
            logger.error(f"保存个人主页信息失败: {e}")
        # Step 2: 遍历多屏作品
        has_look_post_titles = []  # 已处理的作品标题列表
        # 推送到远程服务器
        self.push_to_remote_server(profile_url_file, "profile_url.json")

        while len(has_look_post_titles) < self.analyze_n_posts:
            # 获取当前页面的作品列表
            posts = self.driver.find_elements(
                AppiumBy.XPATH,
                '//android.widget.FrameLayout[starts-with(@content-desc, "笔记,")] | //android.widget.FrameLayout[starts-with(@content-desc, "视频,")]'
            )
            # posts = self.driver.find_elements(
            #     AppiumBy.XPATH,
            #     '//android.widget.FrameLayout[starts-with(@content-desc, "笔记,")]'
            # )
            post_count = len(posts)
            # tiles = [i.get_attribute("content-desc") for i in posts]
            logger.info(f"当前页有 {post_count} 个作品")
            if post_count == 0:
                logger.info("没有找到作品，请检查是否手机端网络配置")
                raise Exception("没有找到作品，请检查是否手机端网络配置")
            # logger.info(f"当前页作品列表: {tiles}")

            # 处理当前页面的每个作品
            for index in range(post_count):
                # 检查是否已达到目标数量
                if len(has_look_post_titles) >= self.analyze_n_posts:
                    break

                # 检查索引是否有效
                if index >= len(posts):
                    logger.warning(f"索引 {index} 超出范围，当前posts数量: {len(posts)}")
                    continue
                post = posts[index]

                try:
                    # 获取作品标题
                    content_desc = post.get_attribute("content-desc")
                    # 如果观看数超过100，需要向上滚动一下

                    # 检查是否已经处理过该作品
                    if content_desc in has_look_post_titles:
                        logger.info(f"作品已处理过，跳过: {content_desc}")
                        continue

                    self.current_post_title = content_desc
                    # logger.info(f"获取到作品标题: {self.current_post_title}")

                    post.click()

                    time.sleep(10)  # 增加等待时间确保页面加载
                    is_video = False

                    if "视频" in self.current_post_title:
                        is_video = True

                    # 判断当前是视频还是图文
                    self.note_link = self._get_invitation_link(is_video)
                    # 使用清理后的文件名
                    self.clean_title = self._clean_filename(self.current_post_title)
                    # if not is_video:
                    # content_element = self.find_element_with_retry(
                    #     AppiumBy.XPATH,
                    #     "//android.widget.TextView[@resource-id='com.xingin.xhs:id/imageNoteTextView']"
                    # )
                    # self.post_content = content_element.get_attribute("text")
                    time.sleep(5)
                    # 以clean_title 为文件名保存 note_link、及post_content 数据，想一个文件格式，后面要读取这个数据，分别出去这2个字段，进行存储

                    self.save_post_data(self.clean_title, self.note_link, self.post_content)

                    # 获取作品的短链
                    self.open_post_menu()
                    has_data_analysis = self._open_data_analysis()
                    if not has_data_analysis:
                        # 如果没有数据分析功能，跳过该作品
                        logger.warning("没有数据分析功能,跳过该作品")
                        self._exit_post()
                        continue
                    self._screenshot_traffic_analysis(is_video)
                    # self._screenshot_audience_analysis()

                    self._screenshot_data_overview(is_video)
                    self._exit_post()

                    # 成功处理后添加到已处理列表
                    has_look_post_titles.append(self.clean_title)
                    logger.info(f"已完成作品处理，当前已处理 {len(has_look_post_titles)} 个作品: {self.clean_title}")
                except Exception as e:
                    logger.warning(f"处理作品失败: {e}")

                    continue
                # 检查是否已达到目标数量
                if len(has_look_post_titles) >= self.analyze_n_posts:
                    logger.info(f"已达到目标数量 {self.analyze_n_posts}，停止处理")
                    break

            # 检查是否已达到目标数量，如果已达到则退出循环
            if len(has_look_post_titles) >= self.analyze_n_posts:
                break

            # 如果还没达到目标数量，则滑动到下一页
            if len(has_look_post_titles) < self.analyze_n_posts:
                logger.info("滑动到下一屏...")
                self.swipe_to_next()
                time.sleep(5)  # 增加等待时间确保页面加载完成

        logger.info(f"任务完成，共处理 {len(has_look_post_titles)} 个作品: {has_look_post_titles}")
        self.normal_end()

    # 保存数据
    def save_post_data(self, clean_title, note_link, post_content):
        """保存帖子链接和内容数据"""
        # home_dir = os.path.expanduser("~")
        # local_dir = os.path.join(home_dir, "ocr", time.strftime('%Y%m%d'), f"{self.deviceName}#{self.accountId}")
        # os.makedirs(self.local_dir, exist_ok=True)

        # 创建数据字典
        data = {
            "note_link": note_link,
            "post_content": post_content,
            "title": self.current_post_title,
            "clean_title": clean_title,
            "timestamp": time.strftime('%Y-%m-%d %H:%M:%S')
        }

        # 保存为JSON文件
        filename = f"{clean_title}.json"
        filepath = os.path.join(self.local_dir, filename)

        with open(filepath, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)

        logger.info(f"帖子数据已保存到: {filepath}")
        # 推送到远程服务
        self.push_to_remote_server(filepath, filename)

    def _get_invitation_link(self, is_video):
        # 刚发布成功会出现在一个元素
        if is_video:
            text_link = self.handle_video_page()
        else:
            text_link = self.handle_note_page()
        return text_link

    def handle_video_page(self):
        logger.info("执行视频动态页操作")
        # 随机滑动 2~10 次
        try:
            invitation_button = self.find_element_with_retry(
                by=AppiumBy.ANDROID_UIAUTOMATOR,
                value='new UiSelector().textContains("分享")',
                timeout=5
            )
        except Exception as e:
            logger.warning(f"获取分享按钮失败: {e}")
            invitation_button = self.find_element_with_retry(
                by=AppiumBy.ANDROID_UIAUTOMATOR,
                value='new UiSelector().description("更多")',
                timeout=5
            )
        invitation_button.click()
        logger.info("滚动寻找分享按钮")
        self.swipe_left_from_button()
        invitation_link = self.find_element_with_retry(
            by=AppiumBy.ANDROID_UIAUTOMATOR,
            value='new UiSelector().text("复制链接")',
            timeout=5
        )
        invitation_link.click()
        pasted_content = self.get_clipboard_text(True)
        text_link = self.extract_xhs_url(pasted_content)
        # print("TEXT_LINK>{}<TEXT_LINK".format(text_link))
        logger.info(f"TEXT_LINK>{text_link}<TEXT_LINK")
        logger.info("获取视频笔记链接成功")
        return text_link

    def get_clipboard_text(self, is_video):
        # 打开系统文本？粘贴，然后读取文本内容？
        # 方法3: 创建临时输入框并粘贴
        # 寻找页面上可能的输入区域
        try:
            # 尝试寻找页面上的编辑框或输入区域
            if is_video:
                
                comment_btn = self.find_element_with_retry(
                                    by=AppiumBy.XPATH,
                                    value='//android.widget.TextView[@text="评论"]',
                                    timeout=5
                                )

                comment_btn.click()
                time.sleep(1)
                comment_input = self.find_element_with_retry(
                    by=AppiumBy.XPATH,
                    value='//android.widget.EditText',
                    timeout=5
                )
                comment_input.click()
                time.sleep(3)


            else:
                # 点击评论输入框
                comment_input = self.find_element_with_retry(
                    by=AppiumBy.XPATH,
                    value='//android.widget.TextView[@content-desc="评论框"]',
                    timeout=5
                )

                comment_input.click()

                time.sleep(3)

            # 模拟粘贴操作 (Ctrl+V)
            self.driver.press_keycode(50, 28672)  # KEYCODE_PASTE with Ctrl
            time.sleep(2)

            input_box = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value='//android.widget.EditText[@resource-id="com.xingin.xhs:id/0_resource_name_obfuscated"]',
            )

            # 获取粘贴后的内容
            pasted_content = input_box.text
            # logger.info(f"通过粘贴操作获取到内容: {pasted_content}")

            time.sleep(5)
            self.driver.press_keycode(4)
            time.sleep(3)
            if is_video:
                self.driver.press_keycode(4)

            return pasted_content
        except Exception as e:
            logger.warning(f"通过页面输入框粘贴获取内容失败: {e}")

    def handle_note_page(self):
        logger.info("执行图文笔记页操作")
        # 随机滑动 2~10 次

        invitation_button = self.find_element_with_retry(
            by=AppiumBy.ANDROID_UIAUTOMATOR,
            value='new UiSelector().resourceId("com.xingin.xhs:id/moreOperateIV")',
            timeout=5
        )
        invitation_button.click()
        logger.info("滚动寻找分享按钮")
        self.swipe_left_from_button()
        invitation_link = self.find_element_with_retry(
            by=AppiumBy.ANDROID_UIAUTOMATOR,
            value='new UiSelector().text("复制链接")',
            timeout=5
        )
        invitation_link.click()
        pasted_content = self.get_clipboard_text(False)
        text_link = self.extract_xhs_url(pasted_content)
        logger.info(f"TEXT_LINK>{text_link}<TEXT_LINK")
        logger.info("获取视频笔记链接成功")
        return text_link

    def swipe_left_from_button(self, duration=400):
        # 定位“置顶笔记”按钮
        try:
            element = self.find_element_with_retry(
                by=AppiumBy.ANDROID_UIAUTOMATOR,
                value='new UiSelector().textContains("编辑")',
                timeout=5
            )
        except Exception as e:
            logger.error(f"找不到按钮: {e}")
            return None

        # 获取滑动的起始点和结束点
        location = element.location
        size = element.size
        center_x = location['x'] + size['width'] / 2
        center_y = location['y'] + size['height'] / 2
        start_x = center_x
        start_y = center_y
        end_x = center_x * 0.6  # 向左滑动
        end_y = center_y

        # 执行滑动操作
        self.driver.swipe(start_x, start_y, end_x, end_y, duration)
        logger.info(f"按钮向左滑动")
        self.driver.swipe(start_x, start_y, end_x, end_y, duration)

    def _enter_profile_page(self):

        logger.info("进入个人主页...")

        profile_btn = self.find_element_with_retry(
            by=AppiumBy.XPATH,
            value="//android.view.ViewGroup[@content-desc='我']",
            timeout=5
        )
        profile_btn.click()
        """进入个人主页"""
        try:
            logger.info("提取昵称、粉丝数、关注数、获赞与收藏数")
            # 提取昵称
            nickname_element = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value='//android.view.View[starts-with(@content-desc, "头像,")]',
                timeout=5
            )
            if nickname_element:
                content_desc = nickname_element.get_attribute("content-desc")
                # content-desc="头像,FunFox"
                nickname = content_desc.split(",")[-1]
                logger.info(f"提取到昵称: {nickname}")

            # 提取关注数
            following_element = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value='//android.widget.Button[contains(@content-desc, "关注")]',
                timeout=5
            )
            if following_element:
                content_desc = following_element.get_attribute("content-desc")
                # content-desc="28关注"
                following_count = content_desc.replace("关注", "")
                logger.info(f"提取到关注数: {following_count}")

            # 提取粉丝数
            follower_element = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value='//android.widget.Button[contains(@content-desc, "粉丝")]',
                timeout=5
            )
            if follower_element:
                content_desc = follower_element.get_attribute("content-desc")
                # content-desc="5粉丝"
                fans = content_desc.replace("粉丝", "")
                logger.info(f"提取到粉丝数: {fans}")

            # 提取获赞与收藏
            likes_collect_element = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value='//android.widget.Button[contains(@content-desc, "获赞与收藏")]',
                timeout=5
            )
            if likes_collect_element:
                content_desc = likes_collect_element.get_attribute("content-desc")
                # content-desc="18获赞与收藏"
                likes_collect_count = content_desc.replace("获赞与收藏", "")
                logger.info(f"提取到获赞与收藏数: {likes_collect_count}")

        except Exception as e:
            logger.error(f"获取个人信息失败: {e}")

        user_profile_url = ""
        try:

            time.sleep(2)
            logger.info("获取个人主页链接")
            
            share_btn = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value='//android.widget.ImageView[@content-desc="分享"]',
                timeout=5
            )
            share_btn.click()
            time.sleep(5)
            profile_url = self.find_element_with_retry(
                by=AppiumBy.ANDROID_UIAUTOMATOR,
                value='new UiSelector().description("复制链接")',
                timeout=5
            )

            profile_url.click()
            time.sleep(10)
            xhs_url = self.get_home_clipboard_text()

            user_profile_url = self.extract_xhs_url(xhs_url)
        except Exception as e:
            logger.error(f"进入个人主页失败: {e}")
            # raise
        # logger.info(f"获取个人主页链接成功: {user_profile_url}")

        return user_profile_url, nickname, following_count, fans, likes_collect_count

    def get_home_clipboard_text(self):
        # 打开系统文本？粘贴，然后读取文本内容？
        # 方法3: 创建临时输入框并粘贴
        # 寻找页面上可能的输入区域
        try:
            # 尝试寻找页面上的编辑框或输入区域
            input_element = self.find_element_with_retry(AppiumBy.XPATH,
                                                         '//android.widget.ImageView[@content-desc="搜索"]')

            if input_element:
                # 使用第一个找到的输入框
                input_element.click()
                time.sleep(1)

                # 模拟粘贴操作 (Ctrl+V)
                self.driver.press_keycode(50, 28672)  # KEYCODE_PASTE with Ctrl
                time.sleep(2)

                input_element = self.find_element_with_retry(AppiumBy.XPATH,
                                                             '//android.widget.EditText')

                # 获取粘贴后的内容
                pasted_content = input_element.text
                # logger.info(f"通过粘贴操作获取到内容: {pasted_content}")
                time.sleep(2)
                self.driver.press_keycode(4)

                posts = self.driver.find_elements(
                    AppiumBy.XPATH,
                    '//android.widget.FrameLayout[starts-with(@content-desc, "笔记,")] | //android.widget.FrameLayout[starts-with(@content-desc, "视频,")]'
                )
                if not posts:
                    self.driver.press_keycode(4)

                return pasted_content
        except Exception as e:
            logger.warning(f"通过页面输入框粘贴获取内容失败: {e}")

    def extract_xhs_url(self, url_text):
        """
        从文本中提取小红书链接
        :param url_text: 包含链接的文本
        :return: 提取到的链接或原始链接
        """
        import re

        # 检查是否是正常的URL格式
        if url_text.startswith(('http://', 'https://')):
            return url_text

        # 如果是包含口令信息的文本，尝试提取其中的链接
        # 匹配小红书的各种链接格式:
        # 1. http://xhslink.com/...
        # 2. https://www.xiaohongshu.com/...
        url_patterns = [
            r'http[s]?://xhslink\.com/[^\s]+',
            r'http[s]?://www\.xiaohongshu\.com/[^\s]+'
        ]

        for pattern in url_patterns:
            match = re.search(pattern, url_text)
            if match:
                extracted_url = match.group(0)
                logger.info(f"从文本中提取到链接: {extracted_url}")
                return extracted_url

        # 如果没有找到链接，返回原始文本
        logger.warning(f"未在文本中找到链接，使用原始文本: {url_text}")
        return url_text.strip()

    def open_post_menu(self):
        """点击图文或视频帖右上角菜单按钮"""
        logger.info("尝试点击右上角菜单按钮...")
        menu_xpaths = [
            '//android.widget.ImageView[@resource-id="com.xingin.xhs:id/moreOperateIV"]',  # 图文帖子
            '//android.widget.ImageView[@content-desc="更多"]'  # 视频帖子
        ]

        for xpath in menu_xpaths:
            try:
                menu_button = self.driver.find_element(
                    by=AppiumBy.XPATH,
                    value=xpath
                )
                menu_button.click()
                logger.info(f"点击菜单按钮")
                time.sleep(10)
                return  # 成功点击后就退出函数
            except Exception as e:
                logger.debug(f"未找到菜单按钮: {xpath}")

        logger.warning("未能找到任何可点击的菜单按钮")
        raise Exception("打开菜单失败，可能不是详情页或页面结构已变化")

    def _open_data_analysis(self):
        """定位并点击数据分析按钮"""
        logger.info("尝试点击数据分析按钮...")

        try:
            analysis_button = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value='//android.widget.Button[@content-desc="数据分析"]',
                timeout=5
            )
            if analysis_button is None:
                logger.warning("未找到数据分析按钮")
                return False
            analysis_button.click()
            logger.info("成功点击数据分析按钮")
            time.sleep(10)
            return True
        except Exception as e:
            logger.error(f"打开数据分析失败: {e}")
            # raise Exception("打开数据分析失败，可能不是详情页或页面结构已变化")
            return False

    def _screenshot_data_overview(self, is_video):
        """截图数据概览页面"""
        logger.info("截图数据概览页面...")
        time.sleep(5)
        traffic_xpaths = [
            # "//android.view.View[@text='数据概览']",
            "//*[contains(@text, '数据概览')]",
            "//android.widget.TextView[contains(@text, '数据概览')]",
            "//android.view.View[contains(@text, '数据概览')]"
        ]
        traffic_button = None
        for xpath in traffic_xpaths:
            traffic_button = self.find_element_with_retry(
                by=AppiumBy.XPATH,
                value=xpath,
                timeout=5
            )
            if traffic_button is None:
                logger.warning("未找到数据概览按钮")
                continue
            traffic_button.click()
            logger.info("成功点击数据概览按钮")
            time.sleep(10)
            break

        # 获取基础数据元素位置并滚动到与数据概览tab同一位置
        if traffic_button:
            # 获取数据概览tab的位置
            tab_location = traffic_button.location
            tab_y = tab_location['y']
            logger.info(f"数据概览tab位置: {tab_y}")

            # 获取基础数据的位置
            screenshot_data = self.driver.get_screenshot_as_png()
            result = self.find_basic_data_position_from_bytes(screenshot_data, text='基础数据')

            if result:
                x, y, w, h = result
                logger.info(f"基础数据位置: y={y}")

                # 计算需要滚动的距离
                scroll_distance = y - tab_y + 200
                logger.info(f"需要滚动的距离: {scroll_distance}")
                logger.info(f"屏幕宽度: {self.screen_width}，屏幕高度: {self.screen_height}")
                # 如果基础数据在tab下方，则向上滚动将基础数据对齐到tab位置
                if scroll_distance > 0:
                    self.driver.swipe(
                        self.screen_width // 2,
                        self.screen_height * 0.2 + scroll_distance / 2,
                        self.screen_width // 2,
                        self.screen_height * 0.2,
                        1000
                    )
                    logger.info(f"屏幕高度：{self.screen_height},已经滚动了{scroll_distance / 2}")
                    time.sleep(3)
                    self.driver.swipe(
                        self.screen_width // 2,
                        self.screen_height * 0.2 + scroll_distance / 2,
                        self.screen_width // 2,
                        self.screen_height * 0.2,
                        1000
                    )
                    logger.info(f"屏幕高度：{self.screen_height},已经滚动了{scroll_distance / 2}")
                    time.sleep(3)
            else:
                logger.warning("未找到基础数据位置")
        else:
            logger.warning("未找到数据概览按钮")

        if is_video:
            filename = f"video_data_overview_top#{self.clean_title}.png"
            # ✅ 新增：将截图保存到服务器指定目录
            screenshot_path = self.save_screenshot(filename)
            self.push_to_remote_server(screenshot_path, filename)
            self.driver.swipe(
                self.screen_width // 2,
                self.screen_height * 0.8,
                self.screen_width // 2,
                self.screen_height * 0.2,
                1000
            )
            filename = f"video_data_overview_bottom#{self.clean_title}.png"
            screenshot_path = self.save_screenshot(filename)
            self.push_to_remote_server(screenshot_path, filename)
        else:
            filename = f"note_data_overview#{self.clean_title}.png"
            # ✅ 新增：将截图保存到服务器指定目录
            screenshot_path = self.save_screenshot(filename)
            self.push_to_remote_server(screenshot_path, filename)

    def find_basic_data_position_from_bytes(self, screenshot_data: bytes, text="基础数据"):
        """
        从字节数据中查找"基础数据"文字的位置信息
        :param screenshot_data: 屏幕截图的字节数据
        :return: "基础数据"文字的位置信息(x, y, width, height)或None
        """
        try:
            # 将字节数据转换为OpenCV图像
            nparr = np.frombuffer(screenshot_data, np.uint8)
            img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)

            if img is None:
                raise ValueError("无法解码图像数据")

            # 转为 PIL 图像格式
            pil_img = Image.fromarray(img)
            # Linux下通常不需要指定tesseract路径，但如果需要可以取消下面一行的注释并设置正确的路径
            # pytesseract.pytesseract.tesseract_cmd = '/usr/bin/tesseract'
            # pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
            # 使用 OCR 识别文字（支持中文）并获取位置信息
            # 使用页面分割模式6来更好地识别连续文本
            data = pytesseract.image_to_data(pil_img, lang='chi_sim+eng', output_type=pytesseract.Output.DICT,
                                             config='--psm 6')

            # 合并同一行上的文本以更好地识别连续文本
            lines = self.merge_text_lines(data)

            # 在合并后的行中查找目标文本"基础数据"
            for i, line in enumerate(lines):
                # 移除文本中的所有空格后再查找
                cleaned_text = ''.join(line['text'].split())
                # 只要包含"基础数据"就匹配
                if text in cleaned_text:
                    x, y, w, h = line['x'], line['y'], line['w'], line['h']
                    print(f"找到文字'{text}': (x={x}, y={y}, width={w}, height={h})")
                    return x, y, w, h

            print(f"未找到'{text}'")
            return None
        except Exception as e:
            print(f"OCR 识别失败: {e}")
            return None

    def merge_text_lines(self, data, y_tolerance=10):
        """
        合并同一行上的文本框

        :param data: pytesseract 返回的数据字典
        :param y_tolerance: Y坐标容差，用于判断是否在同一行
        :return: 合并后的行列表
        """
        lines = []
        n_boxes = len(data['level'])

        # 提取有效的文本框
        boxes = []
        for i in range(n_boxes):
            text = data['text'][i].strip()
            # 去除空字符串但仍显示空白字符
            if len(text) > 0:
                box = {
                    'text': text,
                    'x': data['left'][i],
                    'y': data['top'][i],
                    'w': data['width'][i],
                    'h': data['height'][i]
                }
                boxes.append(box)

        # 根据Y坐标对文本框进行分组
        if not boxes:
            return lines

        # 按Y坐标排序
        boxes.sort(key=lambda b: b['y'])

        # 分组同一行的文本框
        current_line = [boxes[0]]
        for i in range(1, len(boxes)):
            # 如果当前文本框与前一个文本框在同一行，则添加到当前行
            if abs(boxes[i]['y'] - boxes[i - 1]['y']) <= y_tolerance:
                current_line.append(boxes[i])
            else:
                # 否则，结束当前行并将文本框按X坐标排序后合并
                current_line.sort(key=lambda b: b['x'])
                merged_text = ' '.join([b['text'] for b in current_line])

                # 计算合并后的边界框
                min_x = min([b['x'] for b in current_line])
                max_x = max([b['x'] + b['w'] for b in current_line])
                min_y = min([b['y'] for b in current_line])
                max_y = max([b['y'] + b['h'] for b in current_line])

                lines.append({
                    'text': merged_text,
                    'x': min_x,
                    'y': min_y,
                    'w': max_x - min_x,
                    'h': max_y - min_y
                })

                # 开始新行
                current_line = [boxes[i]]

        # 处理最后一行
        if current_line:
            current_line.sort(key=lambda b: b['x'])
            merged_text = ' '.join([b['text'] for b in current_line])

            # 计算合并后的边界框
            min_x = min([b['x'] for b in current_line])
            max_x = max([b['x'] + b['w'] for b in current_line])
            min_y = min([b['y'] for b in current_line])
            max_y = max([b['y'] + b['h'] for b in current_line])

            lines.append({
                'text': merged_text,
                'x': min_x,
                'y': min_y,
                'w': max_x - min_x,
                'h': max_y - min_y
            })

        return lines

    def save_screenshot(self, filename):
        home_dir = os.path.expanduser("~")
        logger.info(f"用户主目录: {home_dir}")
        logger.info(f"当前工作目录: {os.getcwd()}")
        # 使用绝对路径构建目录
        logger.info(f"accountId: {self.accountId}")

        screenshot_path = f"{self.local_dir}/{filename}"
        self.driver.get_screenshot_as_file(screenshot_path)
        logger.info(f"已截图观众分析页面并保存到电脑: {screenshot_path}")
        time.sleep(3)
        # 推送文件到远程服务器
        return screenshot_path

    def push_to_remote_server(self, local_path, filename, target_hostname="172.25.0.30"):
        """将文件推送到远程服务器"""

        if not target_hostname:
            logger.error("无法获取目标hostname")
            return

        try:
            # 创建SSH客户端
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            # 连接到远程服务器
            ssh.connect(target_hostname, username='callfans', password='callfans@123', timeout=10)

            # 创建SFTP客户端
            sftp = ssh.open_sftp()

            # 创建远程目录
            if self.accountId:
                remote_dir = f"/home/callfans/ocr/{self.current_time}/{self.deviceName}#{self.accountId}"
            else:
                remote_dir = f"/home/callfans/ocr/{self.current_time}/{self.deviceName}"
            try:
                # 递归创建目录
                self._create_remote_dir_sftp(sftp, remote_dir)
            except Exception as e:
                logger.warning(f"创建远程目录警告: {e}")
            
            # 上传文件
            remote_path = f"{remote_dir}/{filename}"
            sftp.put(local_path, remote_path)

            # 关闭连接
            sftp.close()
            ssh.close()

            logger.info(f"文件推送成功: {remote_path}")
        except Exception as e:
            logger.error(f"推送过程中发生错误: {e}")

    def _create_remote_dir_sftp(self, sftp, remote_dir):
        """递归创建远程目录"""
        dirs = remote_dir.split('/')
        current_dir = ''

        for dir_name in dirs:
            if dir_name == '':
                current_dir = '/'
                continue
            elif current_dir == '/' or current_dir == '':
                current_dir = f"/{dir_name}"
            else:
                current_dir = f"{current_dir}/{dir_name}"

            try:
                sftp.stat(current_dir)
            except FileNotFoundError:
                try:
                    sftp.mkdir(current_dir)
                    logger.info(f"创建目录: {current_dir}")
                except Exception as e:
                    logger.error(f"创建目录失败 {current_dir}: {e}")

    def _screenshot_traffic_analysis(self, is_video):
        """截图流量分析页面"""
        logger.info("截图流量分析页面...")

        try:
            # 点击流量分析
            # traffic_btn = self.find_element_with_retry(
            #     by=AppiumBy.XPATH,
            #     value="//android.widget.TextView[@text='流量分析']",
            #     timeout=5
            # )
            # if traffic_btn is None:
            #     logger.warning("未找到流量分析按钮，跳过截图")
            #     return
            #
            # traffic_btn.click()
            # time.sleep(2)

            traffic_xpaths = [
                # "//android.view.View[@text='流量分析']",
                "//*[contains(@text, '流量分析')]",
                "//android.widget.TextView[contains(@text, '流量分析')]",
                "//android.view.View[contains(@text, '流量分析')]"
            ]

            traffic_btn = None
            for xpath in traffic_xpaths:
                traffic_btn = self.find_element_with_retry(
                    by=AppiumBy.XPATH,
                    value=xpath,
                    timeout=3
                )
                if traffic_btn is not None:
                    logger.info(f"找到流量分析按钮，使用XPath: {xpath}")
                    break

            if traffic_btn is None:
                logger.warning("未找到流量分析按钮，跳过截图")
                return

                # logger.warning("未找到流量分析按钮，尝试点击坐标位置")
                # # 如果找不到元素，尝试点击大致位置
                # try:
                #     self.driver.tap([(self.screen_width // 2, self.screen_height // 3)])
                #     time.sleep(2)
                # except Exception as tap_error:
                #     logger.warning(f"坐标点击也失败: {tap_error}")
                #     return

            traffic_btn.click()
            logger.info("点击流量分析按钮成功")
            time.sleep(2)
            # 滑到底部
            self.driver.swipe(
                self.screen_width // 2,
                self.screen_height * 0.7,
                self.screen_width // 2,
                self.screen_height * 0.2,
                500
            )
            time.sleep(3)

        except Exception as e:
            logger.warning(f"截图流量分析页面失败: {e}")

        if is_video:
            # 使用清理后的文件名
            filename = f"video_traffic_analysis#{self.clean_title}.png"
        else:
            filename = f"note_traffic_analysis#{self.clean_title}.png"
        # ✅ 新增：将截图保存到服务器指定目录
        screenshot_path = self.save_screenshot(filename)
        self.push_to_remote_server(screenshot_path, filename)

        # screenshot_cmd = f"adb -s {self.deviceName} -P {self.adbPort} shell screencap -p \"/storage/emulated/0/{filename}\""
        # self.execute_adb_command(screenshot_cmd)
        #
        # logger.info(f"已截图流量分析页面，保存到手机: /storage/emulated/0/{filename}")
        # time.sleep(3)

    def _screenshot_audience_analysis(self):
        """截图观众分析页面"""
        logger.info("截图观众分析页面...")

        try:
            # 点击观众分析
            # audience_btn = self.find_element_with_retry(
            #     by=AppiumBy.XPATH,
            #     value="//android.widget.TextView[@text='观众分析']",
            #     timeout=5
            # )
            # if audience_btn is None:
            #     logger.warning("未找到观众分析按钮，跳过截图")
            #     return
            # audience_btn.click()
            # time.sleep(3)

            audience_xpaths = [
                "//android.widget.TextView[@text='观众分析']",
                # "//android.view.View[@text='观众分析']",
                "//*[contains(@text, '观众分析')]",
                "//android.widget.TextView[contains(@text, '观众分析')]",
                "//android.view.View[contains(@text, '观众分析')]"
            ]

            audience_btn = None
            for xpath in audience_xpaths:
                audience_btn = self.find_element_with_retry(
                    by=AppiumBy.XPATH,
                    value=xpath,
                    timeout=3
                )
                if audience_btn is not None:
                    logger.info(f"找到观众分析按钮，使用XPath: {xpath}")
                    break

            if audience_btn is None:
                logger.warning("未找到观众分析按钮，跳过截图")
                return

                # logger.warning("未找到观众分析按钮，尝试点击坐标位置")
                # # 如果找不到元素，尝试点击大致位置
                # try:
                #     self.driver.tap([(self.screen_width // 2, self.screen_height // 2)])
                #     time.sleep(2)
                # except Exception as tap_error:
                #     logger.warning(f"坐标点击也失败: {tap_error}")
                #     return

            if audience_btn is not None:
                audience_btn.click()
                time.sleep(3)

                # 获取两个元素的位置信息
                # note_analysis_element = self.find_element_with_retry(
                #     by=AppiumBy.XPATH,
                #     value="//android.widget.TextView[@text='笔记分析详情']",
                #     timeout=5
                # )

                # # 获取性别分布元素
                # gender_distribution_element = self.find_element_with_retry(
                #     by=AppiumBy.XPATH,
                #     value="//android.widget.TextView[@text='性别分布']",
                #     timeout=5
                # )
                #
                # if note_analysis_element is not None and gender_distribution_element is not None:
                #     # 获取元素的bounds
                #     note_analysis_location = note_analysis_element.location
                #     gender_distribution_location = gender_distribution_element.location
                #
                #     # 计算纵坐标差值
                #     y_diff = gender_distribution_location['y'] - note_analysis_location['y']
                #
                #     # 目标纵坐标差值设为固定值
                #     target_diff = 275
                #     need_scroll = y_diff - target_diff

                # 如果需要滑动，则进行滑动
                # if need_scroll > 0:
                # 向上滑动need_scroll距离
                # 滑到底部
                self.driver.swipe(
                    self.screen_width // 2,
                    self.screen_height * 0.7,
                    # gender_distribution_location['y'] + 50,
                    self.screen_width // 2,
                    self.screen_height * 0.2,
                    # gender_distribution_location['y'] + 50 - need_scroll,
                    800
                )
                time.sleep(3)
            # else:
            #     logger.warning("观众分析页面元素定位失败，跳过滑动")

        except Exception as e:
            logger.warning(f"截图观众分析页面失败: {e}")

        # 使用ADB截图保存到手机
        filename = f"audience_analysis#{self.clean_title}.png"

        # screenshot_cmd = f"adb -s {self.deviceName} -P {self.adbPort} shell screencap -p \"/storage/emulated/0/{filename}\""
        # self.execute_adb_command(screenshot_cmd)
        #
        # logger.info(f"已截图观众分析页面，保存到手机: /storage/emulated/0/{filename}")
        # time.sleep(3)

        # ✅ 新增：将截图保存到服务器指定目录
        screenshot_path = self.save_screenshot(filename)
        self.push_to_remote_server(screenshot_path, filename)

        # pull_cmd = f"adb -s {self.deviceName} -P {self.adbPort} pull \"/storage/emulated/0/{filename}\" \"{local_dir}/{filename}\""
        # self.execute_adb_command(pull_cmd)
        #
        # logger.info(f"已将截图拉取到服务器: {local_dir}/{filename}")
        # time.sleep(3)

    def _exit_post(self, force=False):
        """退出帖子详情页（或强制返回主页）"""

        logger.info("退出当前帖子...")
        self.driver.press_keycode(4)  # Android物理返回键
        time.sleep(5)
        self.driver.press_keycode(4)  # Android物理返回键

    def swipe_to_next(self):
        # 向上滑动
        self.driver.swipe(
            self.screen_width / 2,
            self.screen_height * 0.5,
            self.screen_width / 2,
            self.screen_height * 0.2,
            200
        )

    def find_element_with_retry(self, by, value, timeout=5):
        max_retries = 3
        for attempt in range(max_retries + 1):
            try:
                element = WebDriverWait(self.driver, timeout).until(
                    EC.presence_of_element_located((by, value))
                )
                return element
            except Exception as e:
                if attempt < max_retries:
                    logger.error(f"获取元素失败，重试")
                    time.sleep(3)  # 等待1秒后重试
                else:
                    logger.error(f"获取元素失败，超过3次")
                    logger.error(f"元素位置信息：{value}")
                    return None

    def _clean_filename(self, filename):
        """清理文件名中的特殊字符，将特殊符号、emoji、标点全部改为_，去除空格"""
        import re
        import time

        # 提取关键信息，去掉开头的类型标识(笔记,视频等)
        if filename.startswith(('笔记,', '视频,')):
            filename = filename[3:]  # 去掉"笔记,"或"视频,"

        if "来自" in filename:
            filename = filename.split("来自")[0]

        # 先去除所有空格
        filename = re.sub(r'\s+', '', filename)

        # 特殊处理标签，将 #标签 中的 # 替换为 T_ 前缀表示标签
        # 例如: #网球穿搭 -> T_网球穿搭
        filename = re.sub(r'#([\u4e00-\u9fa5a-zA-Z0-9_]+)', r'T\1', filename)

        # 将所有非中文、英文字母、数字的字符替换为下划线
        cleaned = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', '_', filename)

        # 替换多个连续下划线为单个下划线
        cleaned = re.sub(r'_+', '_', cleaned)

        # 去除首尾下划线
        cleaned = cleaned.strip('_')

        # 限制长度到50个字符
        if len(cleaned) > 80:
            cleaned = cleaned[:80].rstrip('_')  # 防止截断后以_结尾

        # 如果清理后为空，返回默认名称
        return cleaned

    def get_window_size(self):
        size = self.driver.get_window_size()
        return size['width'], size['height']

    def normal_end(self):
        logger.info("返回首页并退出")
        self.driver.press_keycode(3)  # HOME 键
        self.driver.quit()


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('-j', '--job_id', dest='job_id', type=str, default=None, help='target job_id')
    parser.add_argument('-u', '--appium_url', dest='appium_url', type=str, default=None, help='target appium_url')
    parser.add_argument('-d', '--deviceName', dest='deviceName', type=str, default=None, help='target deviceName')
    parser.add_argument('-accountId', '--accountId', dest='accountId', type=str, default="000", help='target accountId')
    parser.add_argument('-s', '--systemPort', dest='systemPort', type=str, default=None, help='target systemPort')
    parser.add_argument('-a', '--adbPort', dest='adbPort', type=str, default=None, help='target adbPort')
    parser.add_argument('-p', '--platformName', dest='platformName', type=str, default=None, help='target platformName')
    parser.add_argument('-ver', '--platformVersion', dest='platformVersion', type=str, default=None,
                        help='target platformVersion')
    parser.add_argument('-n_posts', '--n_posts', dest='analyze_n_posts', type=int, default=2,
                        help='number of recent posts to analyze')  # 添加这一行

    args = parser.parse_args()
    XHSAppiumOperate(**vars(args)).run()
